﻿////////////////////////////////////////////////////////////////
////////////////////////////////////////////////////////////////
//
// BEGIN movesphere2 command
//

#include "StdAfx.h"
#include "PlugIn1PlugIn.h"
#include "rhinoSdkUtilities.h"
#include <chrono>

#pragma region 移动球体动画命令

class CCommandmovesphere2 : public CRhinoCommand
{
public:
	CCommandmovesphere2() = default;
	UUID CommandUUID() override
	{
		// {3A363B9C-699D-41A7-8357-FC0C67FA0F7B}
		static const GUID movesphere2Command_UUID =
		{ 0x3A363B9C, 0x699D, 0x41A7, { 0x83, 0x57, 0xFC, 0x0C, 0x67, 0xFA, 0x0F, 0x7B } };
		return movesphere2Command_UUID;
	}
	const wchar_t* EnglishCommandName() override { return L"jrj5"; }
	CRhinoCommand::result RunCommand(const CRhinoCommandContext& context) override;
};

// The one and only CCommandmovesphere2 object
static class CCommandmovesphere2 themovesphere2Command;

CRhinoCommand::result CCommandmovesphere2::RunCommand(const CRhinoCommandContext& context)
{
	CRhinoCommand::result rc = CRhinoCommand::cancel;

	CRhinoGetPoint gp;
	gp.SetCommandPrompt(L"选择球心");
	gp.GetPoint();
	ON_3dPoint center = gp.Point();
	if (gp.CommandResult() != CRhinoCommand::success)
		return gp.CommandResult();

	center = gp.Point();

	double radius = 0.0;  // 初始化球体半径为 0.0

	CRhinoGetNumber gn;  // 创建一个 CRhinoGetNumber 类的实例，用于获取用户输入的数值
	gn.SetCommandPrompt(L"请输入球体半径");  // 设置命令提示信息，提示用户输入球体半径
	gn.AcceptNothing();  // 允许用户不输入任何值并直接按 Enter 键继续

	// 调用 GetNumber 函数以等待用户输入数值，并将结果存储在 res 变量中
	//CRhinoGet::result res = gn.GetNumber();
	gn.GetNumber();

	// 检查用户是否取消了输入或发生了其他错误
	if (gn.CommandResult() != CRhinoCommand::success)
		return gn.CommandResult();  // 如果命令结果不是 success，则返回错误结果并终止函数
	radius = gn.Number();  // 获取用户输入的数值，并将其赋值给 radius 变量
	//绘制球体

	// 根据用户输入的球心和半径，创建一个球体对象。center 是球心点，radius 是球体的半径
	ON_Sphere sphere(center, radius);

	// 将球体对象转换为旋转曲面形式
	// sphere.RevSurfaceForm(true) 尝试将球体转换为旋转曲面
	// 传入的参数 true 可能表示某种特定的转换选项（具体取决于函数实现）
	// 返回一个指向旋转曲面的指针，如果转换失败则返回 nullptr
	ON_RevSurface* sphere_srf = sphere.RevSurfaceForm(true);

	// 检查旋转曲面转换是否成功
	// 如果 sphere_srf 为 nullptr，说明转换失败，此时返回之前存储的命令执行结果（通常为取消或失败）
	if (!sphere_srf)
		return rc;

	// 创建一个 Rhino 曲面对象，用于在 Rhino 环境中表示和管理这个球体对应的曲面
	CRhinoSurfaceObject* sphere_obj = new CRhinoSurfaceObject();

	// 将之前得到的旋转曲面设置到 Rhino 曲面对象中
	// 这样 sphere_obj 就包含了球体的几何信息
	sphere_obj->SetSurface(sphere_srf);

	// 尝试将创建好的 Rhino 曲面对象添加到当前的 Rhino 文档中
	// context.m_doc 表示当前的 Rhino 文档对象
	// 如果添加成功，AddObject 函数返回 true
	if (context.m_doc.AddObject(sphere_obj))
	{
		// 当对象成功添加到文档后，调用 Redraw 方法刷新文档视图
		// 这样用户就能在界面上看到新添加的球体对象
		context.m_doc.Redraw();

		// 将命令执行结果标记为成功
		rc = CRhinoCommand::success;
	}
	else
	{
		// 如果对象添加失败，说明可能存在内存不足或其他错误
		// 此时需要释放之前分配的 Rhino 曲面对象的内存
		delete sphere_obj;

		// 将指针置为 NULL（在 C++ 中更推荐使用 nullptr），避免悬空指针
		sphere_obj = NULL;
	}
	//context.m_doc.DeleteObject(sphere_obj);// 调用 DeleteObject 方法，从文档中删除 sphere_obj 对象
	/*代码后续可能要实现球体沿着指定曲线移动的动画效果。在动画开始前，先删除初始创建的静态球体对象，
	这样可以避免初始球体和动画过程中创建的球体同时存在于文档中，从而保证动画效果的纯净性和一致性。
	也就是说，初始创建的球体只是为了让用户确认球体的基本属性（如球心和半径），在动画开始时就不再需要它了。*/

	//return rc;

	// 选择要分割的曲线
	CRhinoGetObject go;
	go.SetCommandPrompt(L"选择轨迹");                  // 设置选择提示文本
	go.SetGeometryFilter(CRhinoGetObject::curve_object); // 设置仅允许选择曲线类型
	go.GetObjects(1, 1);                               // 必须选择且只能选择1个对象
	if (go.CommandResult() != success)                  // 检查选择操作是否成功
		return go.CommandResult();

	// 验证选择的曲线对象有效性
	const CRhinoObjRef& crv_obj_ref = go.Object(0);     // 获取第一个选中对象的引用
	const CRhinoObject* crv_obj = crv_obj_ref.Object(); // 提取Rhino对象指针
	const ON_Curve* crv = crv_obj_ref.Curve();          // 提取底层几何曲线指针
	if (0 == crv_obj || 0 == crv)
		return failure;

	// 输入分割的段数
	CRhinoGetInteger gi;
	gi.SetCommandPrompt(L"输入刀具移动的速度");
	gi.SetLowerLimit(1);
	gi.SetUpperLimit(50);
	gi.GetNumber();
	if (gi.CommandResult() != success)
		return gi.CommandResult();

	// 记录总执行开始时间（用于整体性能统计）
	auto start_time1 = std::chrono::high_resolution_clock::now();

	double num_v = gi.Number();//把数字赋值给num_v

	double seg_length = 0.01*num_v; // 每个线段的长度。将输入值转换为切割步长
	bool bReverse = false; // 不按照曲线的反向进行分割
	bool bIncludeEnd = true; // 包含曲线的起始点和结束点

	double crv_length = 0.0;
	crv->GetLength(&crv_length);//计算曲线的长度，将其储存在crv_length中
	RhinoApp().Print(L"曲线的长度是 %f\n", crv_length);
	int seg_count = static_cast<int>(ceil(crv_length / (seg_length * 4))); // 计算需要分割的线段数量ceil 是数学函数，返回 ​不小于输入值的最小整数。
	RhinoApp().Print(L"分割段数为 %d\n", seg_count);

	std::vector<CRhinoObjRef> obj_refs;//储存轨迹对象（std:: 清晰地表明你使用的是 ​标准库中的 vector，
	//而不是其他第三方库或自定义的类型。这增强了代码的可读性和维护性。）
	int obj_count = go.ObjectCount();
	for (int i = 0; i < obj_count; i++)
	{
		const CRhinoObjRef& obj_ref = go.Object(i);
		if (obj_ref != nullptr)
			obj_refs.push_back(obj_ref);
	}

	//int num_segments = gi.Number();

	//通过RhinoDivideCurve（分割对象，分割的段数，曲线的维度，是否创建节点，是否返回参数，节点的容差值，储存分割点参数值的数组）分割曲线
	ON_SimpleArray<double> curve_t; // 声明空数组
	bool r0c = RhinoDivideCurve(*crv, seg_count, 0, false, true, 0, &curve_t);//分割后的曲线的点储存到数组curve_t
	if (!r0c)
	{
		RhinoApp().Print(L"分割曲线时发生了错误\n");
		return failure;
	}

	// If the curve is closed, append the ending domain parameter
	ON_Interval dom = crv->Domain();//获取曲线的参数范围(ON_Interval 用于表示曲线的参数范围（Domain）)
	if (crv->IsClosed())//判断是否是闭合曲线
		curve_t.Append(dom.m_t[1]);//将闭合曲线的终点作为分割点
	/*dom.m_t[0]：参数范围的起始值（起点对应参数）。dom.m_t[1]：参数范围的结束值（终点对应参数）。*/


	std::vector<ON_3dPoint> points;
	for (int i = 0; i < curve_t.Count(); i++)
	{
		ON_3dPoint pt = crv->PointAt(curve_t[i]);
		points.push_back(pt);
		// 处理获得的点坐标
		/*ON_3dPoint point = points[i];
		RhinoApp().Print(L"Point %d: (%f, %f, %f)\n", i, point.x, point.y, point.z);*/
	}

	// 逐步移动球体
	// 删除初始球体对象（可能用于清理前一次操作残留数据）
	context.m_doc.DeleteObject(sphere_obj);  // m_doc 表示 Rhino 文档对象


	// 遍历所有点集生成球体（points.size() 为分割曲线得到的点数）
	for (int i = 0; i < points.size(); i++)
	{
		// 记录单次球体生成的开始时间（用于单次操作性能分析）
		auto start_time2 = std::chrono::high_resolution_clock::now();

		// 创建球体几何定义（以当前点为中心，radius 为半径）
		ON_Sphere spherei(points[i], radius);

		// 将球体转换为旋转曲面（true 表示创建完整球面）
		ON_RevSurface* sphere_srfi = spherei.RevSurfaceForm(true);
		if (!sphere_srfi)  // 异常处理：曲面创建失败时退出
			return rc;

		// 创建 Rhino 曲面对象并绑定几何数据
		CRhinoSurfaceObject* sphere_obji = new CRhinoSurfaceObject();
		sphere_obji->SetSurface(sphere_srfi);

		// 将曲面对象添加到文档并刷新视图（CRhinoDoc::AddObject 返回添加结果）
		if (context.m_doc.AddObject(sphere_obji))
		{
			context.m_doc.Redraw();  // 实时刷新视图显示
			rc = CRhinoCommand::success;
		}
		else  // 添加失败时释放内存
		{
			delete sphere_obji;
			sphere_obji = NULL;
		}

		// 暂停 10ms 实现动画效果（通过延迟显示形成连续视觉效果）
		::Sleep(10);

		// 删除除最后一个外的所有球体（实现逐帧动画的"擦除"效果）
		if (i < points.size() - 1)
		{
			context.m_doc.DeleteObject(sphere_obji);  // 从文档移除当前球体
		}

		// 计算单次球体生成耗时（用于性能优化参考）
		auto end_time2 = std::chrono::high_resolution_clock::now();
		std::chrono::duration<double> duration2 = end_time2 - start_time2;
		double time_taken2 = duration2.count();

		// 输出首次操作的耗时（作为基准参考）
		if (i == 0)
		{
			RhinoApp().Print(L"首次操作的耗时为：%f\n", time_taken2);
		}
	}

	// 计算整体操作耗时（包含所有球体生成和动画延迟）
	auto end_time1 = std::chrono::high_resolution_clock::now();
	std::chrono::duration<double> duration1 = end_time1 - start_time1;
	double time_taken1 = duration1.count();
	RhinoApp().Print(L"整体耗时为：%f\n", time_taken1);  // 输出总耗时


	/*delete curve;
	curve = nullptr;*/
	return CRhinoCommand::success;
}

#pragma endregion

//
// END movesphere2 command
//
////////////////////////////////////////////////////////////////
////////////////////////////////////////////////////////////////
